import type { CreateConnector, InjectedType, WalletDetailsParams, WalletProviderFlags } from '../types/Wallet'
import type { WindowProvider } from '../types'
import { injected } from '@wagmi/vue/connectors'
import { createConnector } from '@wagmi/vue'

function getWindow() {
  return typeof window !== 'undefined' ? (window as WindowProvider) : undefined
}

/**
 * 获取在window对象上显式声明是否安装时的Provider
 *
 * @param flag
 */
function getExplicitInjectedProvider(flag: WalletProviderFlags) {
  // 判断当前window对象是否存在，ssr情况下为undefined
  const _window = getWindow()
  // 如果window对象不存在或者window对象上不存在ethereum，说明没有注入式的Provider，直接return
  if (typeof _window === 'undefined' || typeof _window.ethereum === 'undefined') return
  // 返回ethereum上所有的Provider
  const providers = _window.ethereum.providers

  // 如果Provider存在则返回
  if (providers) {
    return providers.find((provider) => provider[flag])
  }
  // 如果ethereum上存在安装的flag则返回
  if (_window.ethereum[flag]) {
    return _window.ethereum
  }
  // 没有满足的注入式钱包
  return undefined
}

/**
 * 通过命名空间获取`window.namespace`上的Provider，一般为自有链的provider的获取，如果它存在的话。
 * 列如为ronin链时namespace为 `ronin.provider`，使用这个函数时则搜索的时window.ronin.provider是否存在
 * 为particle社媒时为 `particle.provider`，使用这个函数时则搜索的时window.particle.provider是否存在
 * @param namespace
 */
function getWindowProviderNamespace(namespace: string) {
  // 将字符串的xxx.xxx...分开递归分层级调用
  const windowProviderQuery = (provider: any, namespace: string): any => {
    const [property, ...path] = namespace.split('.')
    const _provider = provider[property]
    if (_provider) {
      if (path.length === 0) return _provider
      return windowProviderQuery(_provider, path.join('.'))
    }
  }
  // 判断window存在且已被定义的时候才去搜索provider如果存在就返回
  if (typeof window !== 'undefined') return windowProviderQuery(window, namespace)
}

/**
 * 判断是否含有注入的Provider
 * 这个要在每个walletConnector中去判断是否需要使用walletConnect插件做代理（条件是没有注入式钱包存在的时候）
 * @param flag
 * @param namespace
 * @return boolean
 */
export function hasInjectedProvider({ flag, namespace }: InjectedType) {
  // 判断以命名空间查询Provider是否存在
  if (namespace && typeof getWindowProviderNamespace(namespace) !== 'undefined') {
    return true
  }
  // 判断以flag查询Provider是否存在
  if (flag && typeof getExplicitInjectedProvider(flag) !== 'undefined') {
    return true
  }
  return false
}

/**
 * 获取注入的provider
 * @param flag
 * @param namespace
 */
function getInjectedProvider({ flag, namespace }: InjectedType) {
  const _window = getWindow()
  if (typeof _window === 'undefined') {
    return
  }
  if (namespace) {
    const windowProvider = getWindowProviderNamespace(namespace)
    if (windowProvider) {
      return windowProvider
    }
  }
  if (flag) {
    const provider = getExplicitInjectedProvider(flag)
    if (provider) {
      return provider
    }
  }

  // 如果走上述条件都没有返回对应的Provider，就取window对象下ethereum.providers下的第一个provider作为该钱包的provider
  const providers = _window.ethereum?.providers
  if (typeof providers !== 'undefined' && providers.length > 0) {
    return providers[0]
  }

  // 如果还是没有取到对应的provider则直接使用window.ethereum最为该钱包的provider
  return _window.ethereum
}

/**
 * 创建注入式钱包连接器
 * @param provider
 */
function createInjectedConnector(provider?: any): CreateConnector {
  // 从自定义配置列表传入walletDetails
  return (walletDetails: WalletDetailsParams) => {
    const injectedConfig = provider
      ? {
          // 覆写@wagmi/vue/connectors injected方式的注入目标，重定向为自有插件定义的注入目标（最重要的步骤）
          target: () => ({
            id: walletDetails.nkDetails.id, // 钱包插件id
            name: walletDetails.nkDetails.name, // 钱包插件名称
            provider // 钱包插件在浏览器注入的provider
          })
        }
      : {}

    return createConnector((config) => ({
      ...injected(injectedConfig)(config),
      // 扩展injected连接器的初始化对象，添加nkDetails来在UI部分使用
      ...walletDetails
    }))
  }
}

/**
 * 获取注入式连接器
 * @param namespace
 * @param flag
 * @param target
 */
export function getInjectedConnector({ namespace, flag, target }: InjectedType & { target?: any }): CreateConnector {
  // 判断在获取注入式连接器时是否直接传入target provider，没有传入的话就走传入的flag和namespace获取window上的provider
  const provider = target ? target : getInjectedProvider({ flag, namespace })
  // 返回创建的注入式连接器
  return createInjectedConnector(provider)
}
